import { Scene } from '@babylonjs/core/scene';
import { WebXRSessionManager } from '@babylonjs/core/XR/webXRSessionManager';
import { Quaternion, Vector3 } from '@babylonjs/core/Maths/math.vector';
import { FreeCamera } from '@babylonjs/core/Cameras/freeCamera';
import { UniversalCamera } from '@babylonjs/core/Cameras/universalCamera';
import { WebXRDefaultExperience, WebXRDefaultExperienceOptions } from '@babylonjs/core/XR/webXRDefaultExperience';
import { WebXRState } from '@babylonjs/core/XR/webXRTypes';
import { EventState } from '@babylonjs/core/Misc/observable';
import { CreateHandleTo, ICallback, IHandle, RemoveHandle } from '../data/data-handle';
import { XRInput } from './xr-input/xr-input';
import '@babylonjs/core/Materials/Node/Blocks';
import { WebXRExperienceHelper } from '@babylonjs/core/XR/webXRExperienceHelper';
import { WebXRInput } from '@babylonjs/core/XR/webXRInput';
import { WebXRCamera } from '@babylonjs/core/XR/webXRCamera';
import { CONTROLLERS_REPOSITORY_URLS } from '../highly-coupled-component/custom-constant';
import { WebXRFeaturesManager } from '@babylonjs/core/XR/webXRFeaturesManager';
import { Helper } from '../tool/helper';


/**
 * xr相机参数对象
 * @param position 位置
 * @param target 焦点
 * @param minZ 最小距离
 * @param maxZ 最大距离
 * @param applyGravity 使用重力
 * @param checkCollisions 检测碰撞
 * @param ellipsoid 碰撞范围
 * @param ellipsoidOffset 范围偏移（头部）
 * @param speed 移动速度
 * @param touchAngularSensibility 角度灵敏度
 * @param detached 是否分离控制
 */
interface IXRCameraOptions {
    position?: Vector3,
    target?: Vector3,
    minZ?: number,
    maxZ?: number,
    applyGravity?: boolean,
    checkCollisions?: boolean,
    ellipsoid?: Vector3,
    ellipsoidOffset?: Vector3,
    speed?: number,
    touchAngularSensibility?: number,
    detached?: boolean,
}

/**
 * xr参数对象
 * @param doNotSetCamera 不设置相机参数
 * @param customControllersRepositoryURL 自定义控制器配置地址
 * @param useDefaultEnterExitUi 使用默认沉浸模式切换ui
 * @param cameraOptions 相机的参数对象
 * @param renderingGroupId 渲染顺序值（控制器模型等）
 * @param optionalFeatures 要使用的特性
 * @param disableNearInteraction 禁用近交互
 * @param disablePointerSelection 禁用指针选择 
 */
interface IActiveDefaultXRExperienceOptions {
    doNotSetCamera?: boolean,
    customControllersRepositoryURL?: string,
    useDefaultEnterExitUi?: boolean,
    cameraOptions?: IXRCameraOptions,
    renderingGroupId?: number,
    optionalFeatures?: string[] | boolean,
    disableNearInteraction?: boolean,
    disablePointerSelection?: boolean,
}

/**
 * xr容器接口
 * @param camera 活动相机
 * @param normalCamera 非沉浸模式相机
 * @param immersiveCamera 沉浸模式相机
 * @param experience 原生xr实例
 * @param xrInput vr输入
 * @param helper 原生xr助手
 * @param sessionManager 原生xr会话
 * @param featuresManager 特性管理器
 * @param input 原生xr输入
 * @param onPrepared 添加就位时的回调函数对象
 * @param onEnter 添加进入沉浸模式时的回调函数对象
 * @param onIn 添加进入沉浸模式后的回调函数对象
 * @param onExit 添加退出沉浸模式时的回调函数对象
 * @param onNotIn 添加退出沉浸模式后的回调函数对象
 * @param removePreparedHandle 移除就位时的回调函数对象
 * @param removeEnterHandle 移除进入沉浸模式时的回调函数对象
 * @param removeInHandle 移除进入沉浸模式后的回调函数对象
 * @param removeExitHandle 移除退出沉浸模式时的回调函数对象
 * @param removeNotInHandle 移除退出沉浸模式后的回调函数对象
 */
interface IXRContainer<T> {
    camera: FreeCamera,
    normalCamera: UniversalCamera,
    immersiveCamera: WebXRCamera,
    experience: WebXRDefaultExperience,
    xrInput: XRInput,
    helper: WebXRExperienceHelper,
    sessionManager: WebXRSessionManager,
    featuresManager: WebXRFeaturesManager,
    input: WebXRInput,
    onEnter: { (func: { (data: T): void }): IHandle<T> },
    onIn: { (func: { (data: T): void }): IHandle<T> },
    onExit: { (func: { (data: T): void }): IHandle<T> },
    onNotIn: { (func: { (data: T): void }): IHandle<T> },
    removeEnterHandle: { (handle: number | IHandle<T>): void },
    removeInHandle: { (handle: number | IHandle<T>): void },
    removeExitHandle: { (handle: number | IHandle<T>): void },
    removeNotInHandle: { (handle: number | IHandle<T>): void },
    synchronizeCamera: { (): void },
}


/**
 * ar容器
 */
abstract class XRContainer<T> implements IXRContainer<T> {

    private readonly DEFAULT_POSITION = new Vector3(0, 1.7, 2);
    private readonly DEFAULT_TARGET = new Vector3(0, 1, 0);
    private _enterXRhandles: Array<IHandle<T>> = [];
    private _inXRhandles: Array<IHandle<T>> = [];
    private _exitXRhandles: Array<IHandle<T>> = [];
    private _notInXRhandles: Array<IHandle<T>> = [];

    /**
     * 原生xr实例
     */
    public experience!: WebXRDefaultExperience;

    /**
     * 活动相机
     */
    public camera!: FreeCamera;

    /**
     * 非沉浸模式相机
     */
    public normalCamera!: UniversalCamera;

    /**
     * 沉浸模式相机
     */
    public immersiveCamera!: WebXRCamera;

    /**
     * xr输入
     */
    public xrInput!: XRInput;

    /**
     * 原生xr助手
     */
    public helper!: WebXRExperienceHelper;

    /**
     * 原生xr会话
     */
    public sessionManager!: WebXRSessionManager;

    /**
     * 特性管理器
     */
    public featuresManager!: WebXRFeaturesManager;

    /**
     * 原生xr输入
     */
    public input!: WebXRInput;

    /**
     * 初始化xr实例
     * @param scene 所在场景
     * @param canvas 画布元素
     * @param options 参数对象
     */
    public async ActiveDefaultXRExperience(scene: Scene, canvas: HTMLCanvasElement, options?: IActiveDefaultXRExperienceOptions): Promise<void> {
        const ops = options || {};
        ops.cameraOptions = this.setCameraOptions(ops);
        const cameraWhenSupport = await this.setCamera(scene, canvas, ops);
        this.normalCamera = scene.activeCamera as UniversalCamera;
        this.camera = scene.activeCamera as FreeCamera;
        if (!cameraWhenSupport) return;

        try {
            const url = window.location.origin;
            const isLocalhost = url.includes('localhost');
            const reg = new RegExp(/(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|[0-9])\.((1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)\.){2}(1\d{2}|2[0-4]\d|25[0-5]|[1-9]\d|\d)/, 'i');
            const isIpAddress = reg.test(url);
            const urls: string[] = [];
            if (isLocalhost || isIpAddress) {
                const profiles = url + '/web-xr-profiles';
                urls.push(profiles);
            } else {
                if (ops.customControllersRepositoryURL) urls.push(ops.customControllersRepositoryURL);
            }
            urls.push(...CONTROLLERS_REPOSITORY_URLS);

            try {
                let sus = false;
                for (const url of urls) {
                    const req = new Request(url + '/index.html', {
                        method: 'GET',
                        mode: 'cors',
                    });
                    const rsp = await fetch(req);
                    if (!rsp.ok) continue;
                    const ctn = await rsp.text();
                    const isOk = ctn.includes('custom-web-xr-profiles-url');
                    if (isOk) {
                        sus = true;
                        ops.customControllersRepositoryURL = url;
                        break;
                    }
                }
                if (!sus) {
                    ops.customControllersRepositoryURL = undefined;
                }
            } catch (error) {
                console.warn(error);
            }

            this.experience = await WebXRDefaultExperience.CreateAsync(scene, this.getXROptions(ops));
            if (!ops.useDefaultEnterExitUi) this.experience.enterExitUI.overlay.style.display = 'none';
            this.immersiveCamera = this.experience.baseExperience.camera;
            Helper.markGeneralCamera(this.immersiveCamera);
            this.helper = this.experience.baseExperience;
            this.sessionManager = this.helper.sessionManager;
            this.featuresManager = this.helper.featuresManager;
            this.input = this.experience.input;

            this.initializeCamera(this.experience.baseExperience.camera, ops.cameraOptions);
            this.setInput(scene, ops);
            this.setTeleportation(scene, ops);
            this.setMovement(scene, ops);
            this.setLocomotion(scene, ops);
            this.setStateChange(scene, cameraWhenSupport, ops);

        } catch (err) {
            console.error(err);
        }

    }

    /**
     * 设置xr参数
     * @param option 参数对象
     */
    protected getXROptions(option: IActiveDefaultXRExperienceOptions): WebXRDefaultExperienceOptions {
        return {
            renderingGroupId: option.renderingGroupId || 0,
            optionalFeatures: option.optionalFeatures,
            disableNearInteraction: option.disableNearInteraction || false,
            disablePointerSelection: option.disablePointerSelection || false,
        };
    }

    /**
     * 设置相机参数对象
     * @param options 参数对象
     * @returns 相机参数
     */
    private setCameraOptions(options: IActiveDefaultXRExperienceOptions): IXRCameraOptions {
        const ops = options.cameraOptions || {};
        ops.minZ = ops.minZ || 0.1;
        ops.maxZ = ops.maxZ || 200;
        ops.position = ops.position || this.DEFAULT_POSITION.clone();
        ops.target = ops.target || this.DEFAULT_TARGET.clone();
        ops.applyGravity = !!ops.applyGravity;
        ops.ellipsoid = ops.ellipsoid || new Vector3(0.5, 1, 0.5);
        ops.ellipsoidOffset = ops.ellipsoidOffset || new Vector3(0, 0, 0);
        ops.checkCollisions = !!ops.checkCollisions;
        ops.speed = ops.speed !== undefined ? ops.speed : 1;
        ops.touchAngularSensibility = ops.touchAngularSensibility || 10000;
        return ops;
    }

    /**
     * 设置输入
     * @param scene 所在场景
     * @param options 参数对象
     */
    private setInput(scene: Scene, options: IActiveDefaultXRExperienceOptions): void {
        this.xrInput = new XRInput(this.experience, scene, options);
    }

    /**
     * 设置原地动作移动
     * @param scene 所在场景
     * @param options 参数对象
     */
    protected abstract setLocomotion(scene: Scene, options: IActiveDefaultXRExperienceOptions): void;

    /**
     * 设置控制器移动
     * @param scene 所在场景
     * @param options 参数对象
     */
    protected abstract setMovement(scene: Scene, options: IActiveDefaultXRExperienceOptions): void;

    /**
     * 设置传送移动
     * @param scene 所在场景
     * @param options 参数对象
     */
    protected abstract setTeleportation(scene: Scene, options: IActiveDefaultXRExperienceOptions): void;

    /**
     * 添加进入沉浸模式时的回调函数对象
     * @param func 回调函数
     * @returns 回调函数对象
     */
    public onEnter(func: ICallback<T>): IHandle<T> {
        return CreateHandleTo<T>(func, this._enterXRhandles);
    }

    /**
     * 移除进入沉浸模式时的回调函数对象
     * @param handle 回调函数对象或其id
     */
    public removeEnterHandle(handle: number | IHandle<T>): void {
        RemoveHandle(handle, this._enterXRhandles);
    }

    /**
     * 添加进入沉浸模式后的回调函数对象
     * @param func 回调函数
     * @returns 回调函数对象
     */
    public onIn(func: ICallback<T>): IHandle<T> {
        return CreateHandleTo<T>(func, this._inXRhandles);
    }

    /**
     * 移除进入沉浸模式后的回调函数对象
     * @param handle 回调函数对象或其id
     */
    public removeInHandle(handle: number | IHandle<T>): void {
        RemoveHandle(handle, this._inXRhandles);
    }

    /**
     * 添加退出沉浸模式时的回调函数对象
     * @param func 回调函数
     * @returns 回调函数对象
     */
    public onExit(func: ICallback<T>): IHandle<T> {
        return CreateHandleTo<T>(func, this._exitXRhandles);
    }

    /**
     * 移除退出沉浸模式时的回调函数对象
     * @param handle 回调函数对象或其id
     */
    public removeExitHandle(handle: number | IHandle<T>): void {
        RemoveHandle(handle, this._exitXRhandles);
    }

    /**
     * 添加退出沉浸模式后的回调函数对象
     * @param func 回调函数
     * @returns 回调函数对象
     */
    public onNotIn(func: ICallback<T>): IHandle<T> {
        return CreateHandleTo<T>(func, this._notInXRhandles);
    }

    /**
     * 移除退出沉浸模式后的回调函数对象
     * @param handle 回调函数对象或其id
     */
    public removeNotInHandle(handle: number | IHandle<T>): void {
        RemoveHandle(handle, this._notInXRhandles);
    }

    /**
     * 切换活动相机
     * @param scene 场景
     * @param newCamera 新活动相机
     * @param oldCamera 旧活动相机
     */
    private shiftActiveCamera(scene: Scene, newCamera: FreeCamera, oldCamera?: FreeCamera): void {
        if (scene.activeCameras) {
            for (let i = 0; i < scene.activeCameras.length; i++) {
                if (oldCamera) {
                    if (scene.activeCameras[i].uniqueId === oldCamera.uniqueId) {
                        scene.activeCameras.splice(i, 1);
                        i--;
                        continue;
                    }
                }
                if (scene.activeCameras[i].uniqueId === newCamera.uniqueId) {
                    scene.activeCameras.splice(i, 1);
                    i--;
                }
            }
            scene.activeCameras.push(newCamera);
            scene.activeCamera = newCamera;
        } else {
            scene.activeCameras = [newCamera];
            scene.activeCamera = newCamera;
        }
    }

    /**
     * 设置沉浸模式切换时的行为
     * @param scene 所在场景
     * @param camera 相机
     * @param options 参数对象
     */
    private setStateChange(scene: Scene, camera: FreeCamera, options: IActiveDefaultXRExperienceOptions): void {
        const immersiveCamera = this.experience.baseExperience.camera;
        const curPosition = new Vector3();
        this.experience.baseExperience.onStateChangedObservable.add((state: WebXRState, eventState: EventState): void => {
            switch (state) {
                case WebXRState.ENTERING_XR:
                    curPosition.copyFrom(camera.position);
                    this._enterXRhandles.forEach(handle => {
                        handle.callback(this as any);
                    });
                    break;
                case WebXRState.IN_XR:
                    this.shiftActiveCamera(scene, immersiveCamera, camera);
                    this.camera = immersiveCamera;
                    this.synchronizeCameraInternal(false, false, curPosition);
                    this._inXRhandles.forEach(handle => {
                        handle.callback(this as any);
                    });
                    break;
                case WebXRState.EXITING_XR:
                    curPosition.copyFrom(immersiveCamera.position);
                    this._exitXRhandles.forEach(handle => {
                        handle.callback(this as any);
                    });
                    break;
                case WebXRState.NOT_IN_XR:
                    scene.getEngine().resize(true);
                    this.shiftActiveCamera(scene, camera, immersiveCamera);
                    this.camera = camera;
                    this.synchronizeCameraInternal(false, false, curPosition);
                    this._notInXRhandles.forEach(handle => {
                        handle.callback(this as any);
                    });
                    break;
            }
        });
    }

    /**
     * 同步非沉浸模式相机和沉浸模式相机
     */
    public synchronizeCamera(): void {
        this.synchronizeCameraInternal(true, true);
    }

    private synchronizeCameraInternal(inverse: boolean, transformOnly: boolean, position?: Vector3): void {
        if (!this.immersiveCamera) return;
        if (!this.normalCamera) return;
        let nCam = this.camera instanceof WebXRCamera ? this.immersiveCamera : this.normalCamera;
        let oCam = this.camera instanceof WebXRCamera ? this.normalCamera : this.immersiveCamera;
        if (inverse) {
            let tmp = nCam;
            nCam = oCam;
            oCam = tmp;
        }
        nCam.position.copyFrom(position ? position : oCam.position);
        const dir = Helper.cameraDirection(oCam, new Vector3());
        dir.y = 0;
        if (dir.x === 0 && dir.z === 0) dir.z = -10;
        dir.addInPlace(position ? position : oCam.position);
        nCam.setTarget(dir);
        nCam.computeWorldMatrix();
        if (transformOnly) return;
        let ate = oCam.inputs.attachedToElement;
        if (ate) {
            oCam.detachControl();
            nCam.attachControl(ate, true);
        }
    }

    protected async supportImmersiveMode(): Promise<boolean> {
        return !!navigator.xr;
    }

    /**
     * 设置相机
     * @param scene 所在场景
     * @param canvas 画布元素
     * @param options 参数对象
     * @returns 新创建的相机的promise对象
     */
    private async setCamera(scene: Scene, canvas: HTMLCanvasElement, options: IActiveDefaultXRExperienceOptions): Promise<FreeCamera | null> {
        const camOpt = options.cameraOptions!;
        const needsCamera = (!options.doNotSetCamera) || (!scene.activeCamera) || (!(scene.activeCamera instanceof FreeCamera));
        const supportXR = !!navigator.xr;
        const supportImmersive = await this.supportImmersiveMode();
        let camera: FreeCamera | null = null;
        if (!supportXR) {
            if (needsCamera) {
                camera = this.createFreeCamera(scene, canvas, camOpt);
                this.shiftActiveCamera(scene, camera);
            }
            return null;
        }

        if (needsCamera) {
            camera = this.createFreeCamera(scene, canvas, camOpt);
            this.shiftActiveCamera(scene, camera);
        }
        camera = scene.activeCamera as FreeCamera;
        if (!supportImmersive) {
            return null;
        }
        return camera;
    }

    /**
     * 创建通用相机
     * @param scene 所在场景
     * @param canvas 画布元素
     * @param options 参数对象
     * @returns 通用相机
     */
    private createFreeCamera(scene: Scene, canvas: HTMLCanvasElement, options: IXRCameraOptions): FreeCamera {
        const camera = new UniversalCamera('xr-main-free-camera', options.position!.clone(), scene);
        Helper.markGeneralCamera(camera);
        this.initializeCamera(camera, options);
        camera.touchAngularSensibility = options.touchAngularSensibility!;
        if (!options.detached) camera.attachControl(canvas, true);
        return camera;
    }

    /**
     * 初始化相机设置
     * @param camera 目标相机
     * @param options 参数对象
     */
    protected initializeCamera(camera: FreeCamera, options: IXRCameraOptions): void {
        camera.position.copyFrom(options.position!);
        camera.setTarget(options.target!.clone());
        camera.rotationQuaternion = Quaternion.FromEulerVector(camera.rotation);
        camera.minZ = options.minZ!;
        camera.maxZ = options.maxZ!;
        camera.applyGravity = options.applyGravity!;
        camera.ellipsoid = options.ellipsoid!;
        camera.ellipsoidOffset = options.ellipsoidOffset!;
        camera.checkCollisions = options.checkCollisions!;
        camera.speed = options.speed!;
        if (options.detached) camera.detachControl();
    }

}


export { XRContainer };
export type { IActiveDefaultXRExperienceOptions, IXRContainer, IXRCameraOptions };